| @@ -14,6 +14,26 @@ class UserCredentialsController < ApplicationController | ||
| 14 | 14 | end | 
| 15 | 15 | end | 
| 16 | 16 |  | 
| 17 | + def import | |
| 18 | + if params[:file] | |
| 19 | + file = params[:file] | |
| 20 | + content = JSON.parse(file.read) | |
| 21 | + new_credentials = content.map do |hash| | |
| 22 | +        current_user.user_credentials.build(hash.slice("credential_name", "credential_value", "mode")) | |
| 23 | + end | |
| 24 | + | |
| 25 | + respond_to do |format| | |
| 26 | + if new_credentials.map(&:save).all? | |
| 27 | +          format.html { redirect_to user_credentials_path, notice: "The file was successfully uploaded."} | |
| 28 | + else | |
| 29 | +          format.html { redirect_to user_credentials_path, notice: 'One or more of the uploaded credentials was not imported due to an error. Perhaps an existing credential had the same name?'} | |
| 30 | + end | |
| 31 | + end | |
| 32 | + else | |
| 33 | + redirect_to user_credentials_path, notice: "No file was chosen to be uploaded." | |
| 34 | + end | |
| 35 | + end | |
| 36 | + | |
| 17 | 37 | def new | 
| 18 | 38 | @user_credential = current_user.user_credentials.build | 
| 19 | 39 |  | 
| @@ -39,6 +39,28 @@ | ||
| 39 | 39 | <div class="btn-group"> | 
| 40 | 40 | <%= link_to new_user_credential_path, class: "btn btn-default" do %><span class="glyphicon glyphicon-plus"></span> New Credential<% end %> | 
| 41 | 41 | <%= link_to user_credentials_path(format: :json), class: "btn btn-default" do %><span class="glyphicon glyphicon-cloud-download"></span> Download Credentials<% end %> | 
| 42 | +        <%= link_to '#', data: { toggle: 'modal', target: '#credentials-upload' }, class: "btn btn-default credentials-upload-button" do %><span class="glyphicon glyphicon-upload"></span> Upload Credentials<% end %> | |
| 43 | + </div> | |
| 44 | + | |
| 45 | + <div id="credentials-upload" class="modal fade" tabindex="-1" role="dialog"> | |
| 46 | + <div class="modal-dialog"> | |
| 47 | + <div class="modal-content"> | |
| 48 | + <%= form_tag import_user_credentials_path, multipart: true do %> | |
| 49 | + <div class="modal-header"> | |
| 50 | + <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button> | |
| 51 | + <h4 class="modal-title">Upload Credentials</h4> | |
| 52 | + </div> | |
| 53 | + <div class="modal-body"> | |
| 54 | + <p>Upload a credentials file that you have previously exported from a Huginn instance.</p> | |
| 55 | + <%= file_field_tag :file, class: 'form-control' %> | |
| 56 | + </div> | |
| 57 | + <div class="modal-footer"> | |
| 58 | + <%= button_tag 'Cancel', class: 'btn btn-default', 'data-dismiss' => 'modal' %> | |
| 59 | + <%= submit_tag 'Upload', class: 'btn btn-primary' %> | |
| 60 | + </div> | |
| 61 | + <% end %> | |
| 62 | + </div> | |
| 63 | + </div> | |
| 42 | 64 | </div> | 
| 43 | 65 | </div> | 
| 44 | 66 | </div> | 
| @@ -48,7 +48,11 @@ Huginn::Application.routes.draw do | ||
| 48 | 48 | resource :diagram, :only => [:show] | 
| 49 | 49 | end | 
| 50 | 50 |  | 
| 51 | - resources :user_credentials, :except => :show | |
| 51 | + resources :user_credentials, :except => :show do | |
| 52 | + collection do | |
| 53 | + post :import | |
| 54 | + end | |
| 55 | + end | |
| 52 | 56 |  | 
| 53 | 57 | resources :services, :only => [:index, :destroy] do | 
| 54 | 58 | member do | 
| @@ -10,6 +10,7 @@ describe UserCredentialsController do | ||
| 10 | 10 |  | 
| 11 | 11 | before do | 
| 12 | 12 | sign_in users(:bob) | 
| 13 | +    @file = fixture_file_upload('user_credentials.json') | |
| 13 | 14 | end | 
| 14 | 15 |  | 
| 15 | 16 | describe "GET index" do | 
| @@ -30,6 +31,26 @@ describe UserCredentialsController do | ||
| 30 | 31 | end | 
| 31 | 32 | end | 
| 32 | 33 |  | 
| 34 | + describe "Post import" do | |
| 35 | + it "asserts user credentials were created for current user only" do | |
| 36 | + post :import, :file => @file | |
| 37 | + expect(controller.current_user.id).to eq(users(:bob).id) | |
| 38 | + expect(controller.current_user.user_credentials).to eq(users(:bob).user_credentials) | |
| 39 | + end | |
| 40 | + | |
| 41 | + it "asserts that primary id in json file is ignored" do | |
| 42 | + post :import, :file => @file | |
| 43 | + expect(controller.current_user.user_credentials.last.id).not_to eq(24) | |
| 44 | + end | |
| 45 | + | |
| 46 | + it "duplicate credential name shows an error that it is not saved" do | |
| 47 | +      file1 = fixture_file_upload('multiple_user_credentials.json') | |
| 48 | + post :import, :file => file1 | |
| 49 | +      expect(flash[:notice]).to eq("One or more of the uploaded credentials was not imported due to an error. Perhaps an existing credential had the same name?") | |
| 50 | + expect(response).to redirect_to(user_credentials_path) | |
| 51 | + end | |
| 52 | + end | |
| 53 | + | |
| 33 | 54 | describe "POST create" do | 
| 34 | 55 | it "creates UserCredentials for the current user" do | 
| 35 | 56 |        expect { | 
| @@ -0,0 +1,38 @@ | ||
| 1 | +[ | |
| 2 | +  { | |
| 3 | + "id": 23, | |
| 4 | + "user_id": 30, | |
| 5 | + "credential_name": "Google_api_key", | |
| 6 | + "credential_value": "gcperfxrtqymqmluvskxzyiyxxfnjduzncoukyqehkrkamofwz", | |
| 7 | + "created_at": "2016-04-01 10:50:59 -0700", | |
| 8 | + "updated_at": "2016-04-01 10:50:59 -0700", | |
| 9 | + "mode": "text" | |
| 10 | + }, | |
| 11 | +  { | |
| 12 | + "id": 24, | |
| 13 | + "user_id": 30, | |
| 14 | + "credential_name": "twitter_secret_key", | |
| 15 | + "credential_value": "jhpswiebwhbrnabgkbvczrwcyxblxtyvvlvkhuoudjalcqmlwz", | |
| 16 | + "created_at": "2016-04-01 10:50:59 -0700", | |
| 17 | + "updated_at": "2016-04-01 10:50:59 -0700", | |
| 18 | + "mode": "text" | |
| 19 | + }, | |
| 20 | +  { | |
| 21 | + "id": 23, | |
| 22 | + "user_id": 30, | |
| 23 | + "credential_name": "Google_api_key", | |
| 24 | + "credential_value": "gcperfxrtqymqmluvskxzyiyxxfnjduzncoukyqehkrkamofwz", | |
| 25 | + "created_at": "2016-04-01 10:50:59 -0700", | |
| 26 | + "updated_at": "2016-04-01 10:50:59 -0700", | |
| 27 | + "mode": "text" | |
| 28 | + }, | |
| 29 | +  { | |
| 30 | + "id": 24, | |
| 31 | + "user_id": 30, | |
| 32 | + "credential_name": "twitter_secret_key", | |
| 33 | + "credential_value": "jhpswiebwhbrnabgkbvczrwcyxblxtyvvlvkhuoudjalcqmlwz", | |
| 34 | + "created_at": "2016-04-01 10:50:59 -0700", | |
| 35 | + "updated_at": "2016-04-01 10:50:59 -0700", | |
| 36 | + "mode": "text" | |
| 37 | + } | |
| 38 | +] | 
| @@ -0,0 +1,20 @@ | ||
| 1 | +[ | |
| 2 | +  { | |
| 3 | + "id": 23, | |
| 4 | + "user_id": 30, | |
| 5 | + "credential_name": "Google_api_key", | |
| 6 | + "credential_value": "gcperfxrtqymqmluvskxzyiyxxfnjduzncoukyqehkrkamofwz", | |
| 7 | + "created_at": "2016-04-01 10:50:59 -0700", | |
| 8 | + "updated_at": "2016-04-01 10:50:59 -0700", | |
| 9 | + "mode": "text" | |
| 10 | + }, | |
| 11 | +  { | |
| 12 | + "id": 24, | |
| 13 | + "user_id": 30, | |
| 14 | + "credential_name": "twitter_secret_key", | |
| 15 | + "credential_value": "jhpswiebwhbrnabgkbvczrwcyxblxtyvvlvkhuoudjalcqmlwz", | |
| 16 | + "created_at": "2016-04-01 10:50:59 -0700", | |
| 17 | + "updated_at": "2016-04-01 10:50:59 -0700", | |
| 18 | + "mode": "text" | |
| 19 | + } | |
| 20 | +] |